package com.vhukze.basics.config;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.vhukze.basics.entity.dto.RestfulResp;
import com.vhukze.basics.enumeration.RespCodeEnum;
import com.vhukze.basics.exception.MyException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.validation.ConstraintViolationException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 处理代码中抛出的异常. 但是不会处理接管404之类的处理.
 * GlobalExceptionHandler.java类中的@ControllerAdvice注解只处理经过Controller的不经过Controller的不进行处理
 */
@RestControllerAdvice
public class GlobalControllerException implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> aClass,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {

        if (body == null) {
            return new RestfulResp<>().success();
        } else if (body instanceof RestfulResp) {
            return body;
        } else if (body instanceof String) {
            return JSONUtil.toJsonStr(new RestfulResp<>().success(body));
        } else {
            ServletServerHttpResponse httpResponse = (ServletServerHttpResponse) response;
            if (httpResponse.getServletResponse().getStatus() == 200) {
                return new RestfulResp<>().success(body);
            } else {
                return body;
            }
        }

    }

    private static final String MSG = "接口返回报错，联系管理员查看日志";
    private static final String error = "代码内部报错";
    private static final String path = new File("").getAbsolutePath() + File.separator +
            "logs" + File.separator + "错误信息.log";

    //其他参数异常
    @Order(2)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = BindException.class)
    public RestfulResp<?> exception(BindException e) {
        this.writeMsg(RespCodeEnum.PARAM_ERR.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().enumResp(RespCodeEnum.PARAM_ERR);
    }

    //parameter异常
    //为了安全，就不将报错信息返回到前端，只返回粗略信息
    @Order(1)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ConstraintViolationException.class)
    public RestfulResp<?> exception(ConstraintViolationException e) {
        this.writeMsg(RespCodeEnum.PARAM_ERR.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().enumResp(RespCodeEnum.PARAM_ERR);
    }

    //bean异常
    //为了安全，就不将报错信息返回到前端，只返回粗略信息
    @Order(1)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public RestfulResp<?> exception(MethodArgumentNotValidException e) {
        this.writeMsg(RespCodeEnum.PARAM_ERR.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().enumResp(RespCodeEnum.PARAM_ERR);
    }

    // 传参类型不对
    @Order(1)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public RestfulResp<?> exception(MethodArgumentTypeMismatchException e) {
        e.printStackTrace();
        this.writeMsg(RespCodeEnum.PARAM_ERR.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().enumResp(RespCodeEnum.PARAM_ERR);
    }

    //我的自定义异常
    @Order(1)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MyException.class)
    public RestfulResp<?> exception(MyException e) {
        e.printStackTrace();
        if (e.getRespCodeEnum() != null) {
            this.writeMsg(e.getRespCodeEnum().getMessage(), e.getStackTrace()[0], null);
            return new RestfulResp<>().enumResp(e.getRespCodeEnum());
        }
        if (e.getMsg() != null) {
            this.writeMsg(e.getMessage(), e.getStackTrace()[0], null);
            return new RestfulResp<>().error(e.getMessage(), e.getCode());
        }
        return new RestfulResp<>().unknown();
    }

    //运行时异常
    @Order(999)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(RuntimeException.class)
    public RestfulResp<?> exception(RuntimeException e) {
        e.printStackTrace();
        this.writeMsg(error, e.getStackTrace()[0], null);
        return new RestfulResp<>().unknown(MSG);
    }

    // 空指针异常
    @Order(1)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(NullPointerException.class)
    public RestfulResp<?> exception(NullPointerException e) {
        e.printStackTrace();
        this.writeMsg("空指针", e.getStackTrace()[0], null);
        return new RestfulResp<>().unknown(MSG);
    }

    //未知异常
    @Order
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public RestfulResp<?> exception(Exception e) {
        e.printStackTrace();
        this.writeMsg(error, e.getStackTrace()[0], null);
        return new RestfulResp<>().unknown(MSG);
    }

    // 无角色
    @Order(1)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(NotRoleException.class)
    public RestfulResp<?> exception(NotRoleException e) {
        e.printStackTrace();
        this.writeMsg(e.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().error(e.getMessage(), 403);
    }

    // 无权限
    @Order(1)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(NotPermissionException.class)
    public RestfulResp<?> exception(NotPermissionException e) {
        e.printStackTrace();
        this.writeMsg(e.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().error(e.getMessage(), 403);
    }

    // 无token
    @Order(1)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(NotLoginException.class)
    public RestfulResp<?> exception(NotLoginException e) {
        e.printStackTrace();
        this.writeMsg(e.getMessage(), e.getStackTrace()[0], null);
        return new RestfulResp<>().error(e.getMessage(), 403);
    }

    // 无法连接到数据库
    @Order(1)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(MyBatisSystemException.class)
    public RestfulResp<?> exception(MyBatisSystemException e) {
        e.printStackTrace();

        if (e.getMessage() != null && (e.getMessage().contains("尝试连线已失败。") ||
                e.getMessage().contains("password authentication failed for user"))) {

            this.writeMsg(RespCodeEnum.CONN_DB_FAIL.getMessage(), e.getStackTrace()[0], null);
            return new RestfulResp<>().enumResp(RespCodeEnum.CONN_DB_FAIL);
        }
        this.writeMsg(error, e.getStackTrace()[0], null);
        return new RestfulResp<>().unknown();
    }

    // SQL语法错误
    @Order(1)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(BadSqlGrammarException.class)
    public RestfulResp<?> exception(BadSqlGrammarException e) {
        e.printStackTrace();

        String info = e.getMessage() == null ? null : e.getMessage().split("### SQL:")[1];
        this.writeMsg("sql执行错误", e.getStackTrace()[0], info);
        return new RestfulResp<>().enumResp(RespCodeEnum.SQL_ERR);
    }

    private void writeMsg(String msg, StackTraceElement traceElement, String info) {
        if (!FileUtil.exist(path)) {
            FileUtil.touch(path);
        }
        try (BufferedWriter writer = FileUtil.getWriter(path, StandardCharsets.UTF_8, true)) {
            writer.write(DateTime.now() + "  --  " + msg + "  :  方法" + traceElement.getClassName()
                    + "  ->  " + traceElement.getMethodName() + "  ->  " + traceElement.getLineNumber() + "行");
            if (StrUtil.isNotBlank(info)) {
                writer.newLine();
                writer.write("错误详细信息: " + info);
            }
            writer.newLine();
            writer.newLine();
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
